package com.agileboot.api.controller.login;

import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.extra.servlet.ServletUtil;
import com.agileboot.api.customize.ApiTokenUtils;
import com.agileboot.api.customize.config.ApiSecurityConfig;
import com.agileboot.common.config.AgileBootConfig;
import com.agileboot.common.enums.common.LoginStatusEnum;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.common.utils.ServletHolderUtil;
import com.agileboot.common.utils.ip.IpRegionUtil;
import com.agileboot.domain.app.user.command.AppLoginDTO;
import com.agileboot.domain.app.user.db.AppUserEntity;
import com.agileboot.domain.app.user.db.AppUserInfoEntity;
import com.agileboot.domain.app.user.db.AppUserService;
import com.agileboot.domain.app.user.model.AppUserModelFactory;
import com.agileboot.domain.app.user.model.AppUserModel;
import com.agileboot.domain.common.async.AsyncTaskFactory;
import com.agileboot.domain.common.cache.RedisCacheService;
import com.agileboot.infrastructure.cache.RedisUtil;
import com.agileboot.infrastructure.cache.aop.CacheNameConstants;
import com.agileboot.infrastructure.thread.ThreadPoolManager;
import com.agileboot.infrastructure.user.app.AppLoginUser;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class ApiUserService {

    /** 模型驱动 */
    private final AppUserModelFactory appUserModelFactory;
    /** 用户服务 */
    private final AppUserService userService;

    private final AuthenticationManager authenticationManager;
    /** token 工具类 */
    private final ApiTokenUtils apiTokenUtils;
    // 因为是短信登录、注册，所以固定一个假密码。
    private final String password = "admin123";


    /**
     * 登录或注册
     *
     * @param bean data
     * @return 返回结果
     */
    public String loginOrRegister(AppLoginDTO bean) {

        bean.setPhone(decryptTxt(bean.getPhone().trim()));

        // 手机号异常
        if (!PhoneUtil.isPhone(bean.getPhone())) {
            throw new ApiException(ErrorCode.Business.USER_PHONE_NUMBER_IS_ERROR);
        }

        // 验证码
        String cacheObject = redisUtil.getCacheObject(CacheNameConstants.getCaptchaCodeKey(bean.getPhone()));
        if (!StringUtils.equals(bean.getCaptchaCode(), cacheObject)) {
            throw new ApiException(ErrorCode.Business.LOGIN_CAPTCHA_CODE_WRONG);
        }

        // todo 2024年4月1日 11:48:18 这种写法可能有问题，异步问题，注册还没有执行完成，就执行登录了
        if (userService.getUserByUserName(bean.getPhone()) == null) {
            // 注册
            String registerUserId = register(bean);

            if (!StrUtil.isNotEmpty(registerUserId)) {
                throw new ApiException(ErrorCode.Business.REGISTER_ERROR);
            }
        }


        Authentication authenticate;
        try {
            // 通过security效验
            // 该方法会去调用UserDetailsServiceImpl# loadUserByUsername(前端明文端账号,前端明文的密码)
            // 这里传入的[明文密码]，必须和数据库中查询出来的加密后的密码匹配,具体【明文】与加【密字符串】对比是security框架去处理，否则会抛出异常
            authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(bean.getPhone(), password));
        } catch (BadCredentialsException e) {
            throw new ApiException(e, ErrorCode.Business.LOGIN_WRONG_USER_PASSWORD);
        } catch (Exception e) {
            throw new ApiException(e, ErrorCode.Business.LOGIN_ERROR, e.getMessage());
        }

        //  security验证成功后,将用户信息放入上下文
        SecurityContextHolder.getContext().setAuthentication(authenticate);
        AppLoginUser principal = (AppLoginUser) authenticate.getPrincipal();
        // 记录登录信息
        recordLoginInfo(principal);

        // 验证码使用后,置空缓存
        redisUtil.setCacheObject(CacheNameConstants.getCaptchaCodeKey(bean.getPhone()), null);

        // 生成token并放入缓存
        return apiTokenUtils.createTokenAndPutUserInCache(principal);

    }

    private final ApiSecurityConfig securityConfig;


    /**
     * 注册用户
     *
     * @param bean
     * @return
     */
    @Transactional
    public String register(AppLoginDTO bean) {
        AppUserModel model = appUserModelFactory.create();
        String ipSite = ServletUtil.getClientIP(ServletHolderUtil.getRequest());
        bean.setApplyIp(ipSite);
        bean.setLocation(IpRegionUtil.getBriefLocationByIp(ipSite));
        model.setCreateTime(new Date());
        model.loadAddCommand(bean);

        // 手动生成一个Uuid
        model.setUserId(IdUtil.simpleUUID());
        model.setPassword(securityConfig.encoderPassword(password));
        // 插入用户表
        model.insert();
        // 插入并返回id
        String userId = model.getUserId();

        if (StrUtil.isNotEmpty(userId)) {
            // 插入用户信息表(用户表+用户信息表关联)
            AppUserInfoEntity appUserInfo = appUserModelFactory.createAppUserInfo();
            appUserInfo.setUserId(userId);
            appUserInfo.setAvailableCredit(1000);
            appUserInfo.insert();
        }
        return userId;
    }


    private final RedisCacheService redisCacheService;

    private final RedisUtil redisUtil;

    /**
     * 登录成功后，记录登录信息
     *
     * @param loginUser 登录用户
     */
    public void recordLoginInfo(AppLoginUser loginUser) {
        ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginUser.getUsername(), LoginStatusEnum.LOGIN_SUCCESS, LoginStatusEnum.LOGIN_SUCCESS.description()));
        AppUserEntity entity = redisCacheService.appCacheEntityGet.getObjectById(loginUser.getUserId());
        entity.setIp(ServletUtil.getClientIP(ServletHolderUtil.getRequest()));
        entity.setLoginDate(DateUtil.date());
        entity.updateById();
    }


    /**
     * 发送短信
     *
     * @param loanCommand 贷款申请命令对象
     * @throws ApiException 当手机号码为空、请求频繁、验证码生成失败时抛出
     */
    public void sendSms(AppLoginDTO loanCommand) {

        loanCommand.setPhone(decryptTxt(loanCommand.getPhone().trim()));

        if (!PhoneUtil.isPhone(loanCommand.getPhone())) {
            throw new ApiException(ErrorCode.Business.USER_PHONE_NUMBER_IS_ERROR);
        }

        // 限制每个手机号一天只能发送5次验证码
        int countSms = -1;
        try {
            countSms = redisUtil.getCacheObject(CacheNameConstants.getSmsCountKey(loanCommand.getPhone()));
        } catch (Exception e) {
            countSms = 0;
        }

        if (countSms >= 5) {
            throw new ApiException(ErrorCode.Client.COMMON_REQUEST_TOO_OFTEN);
        }

        // 验证码是否过期
        String cacheObject = redisUtil.getCacheObject(CacheNameConstants.getCaptchaCodeKey(loanCommand.getPhone()));
        if (cacheObject != null) {
            throw new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "短信次数已达到上限");
        }

        // 生成短信验证码
        String code = new RandomGenerator("0123456789", 6).generate();
        code = "123456";

        // 调用发送短信
        String testUsername = "qqzdxb"; //在短信宝注册的用户名
        String testPassword = "134520qqz"; //在短信宝注册的密码
        String testPhone = loanCommand.getPhone();
        // 注意测试时，也请带上公司简称或网站签名，发送正规内容短信。千万不要发送无意义的内容：例如 测一下、您好。否则可能会收不到
        String testContent = String.format("【鑫盛源】您的验证码是%s,５分钟内有效。若非本人操作请忽略此消息。", code);

        String httpUrl = "http://api.smsbao.com/sms";

        StringBuffer httpArg = new StringBuffer();
        httpArg.append("u=").append(testUsername).append("&");
        httpArg.append("p=").append(md5(testPassword)).append("&");
        httpArg.append("m=").append(testPhone).append("&");
        try {
            httpArg.append("c=").append(URLEncoder.encode(testContent, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

        String result = "0";
//        result = HttpUtil.get(httpUrl + "?" + httpArg.toString());
        if (!result.equals("0")) {
            //   30	错误密码
            //   40	账号不存在
            //   41	余额不足
            //   43	IP地址限制
            //   50	内容含有敏感词
            //   51 手机号码不正确


            throw new ApiException(ErrorCode.Internal.CAPTCHA_CODE_CALL_FAIL);
        }

        // 每发送成功一次，记录一次
        redisUtil.setCacheObject(CacheNameConstants.getSmsCountKey(loanCommand.getPhone()), countSms + 1, 6, TimeUnit.HOURS);

        // 验证码放入缓存，60秒过期
        redisUtil.setCacheObject(CacheNameConstants.getCaptchaCodeKey(loanCommand.getPhone()), code, 60, TimeUnit.SECONDS);
        System.out.printf("生成的验证码%s%n", code);
    }

    /**
     * 验证短信验证码
     *
     * @param originalPassword 加密后的验证码
     * @return 验证结果
     */
    public String decryptTxt(String originalPassword) {
        byte[] decryptBytes = SecureUtil.rsa(AgileBootConfig.getRsaPrivateKey(), null).decrypt(Base64.decode(originalPassword), KeyType.PrivateKey);
        return StrUtil.str(decryptBytes, CharsetUtil.CHARSET_UTF_8);
    }


    public static String md5(String plainText) {
        StringBuffer buf = null;
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(plainText.getBytes());
            byte b[] = md.digest();
            int i;
            buf = new StringBuffer("");
            for (int offset = 0; offset < b.length; offset++) {
                i = b[offset];
                if (i < 0) i += 256;
                if (i < 16) buf.append("0");
                buf.append(Integer.toHexString(i));
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return buf.toString();
    }

    public static String encodeUrlString(String str, String charset) {
        String strret = null;
        if (str == null) return str;
        try {
            strret = java.net.URLEncoder.encode(str, charset);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return strret;
    }
}
